// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_GL_GPU_TIMING_H_
#define UI_GL_GPU_TIMING_H_

#include <stdint.h>

#include <memory>
#include <queue>

#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "ui/gl/gl_export.h"

// The gpu_timing classes handles the abstraction of GL GPU Timing extensions
// into a common set of functions. Currently the different timer extensions that
// are supported are EXT_timer_query, ARB_timer_query and
// EXT_disjoint_timer_query.
//
// Explanation of Classes:
//   GPUTiming - GPU Timing is a private class which is only owned by the
//     underlying GLContextReal class. This class handles any GL Context level
//     states which may need to be redistributed to users of GPUTiming. For
//     example, there exists only a single disjoint flag for each real GL
//     Context. Once the disjoint flag is checked, internally it is reset to
//     false. In order to support multiple virtual contexts each checking the
//     disjoint flag seperately, GPUTiming is in charge of checking the
//     disjoint flag and broadcasting out the disjoint state to all the
//     various users of GPUTiming (GPUTimingClient below).
//   GPUTimingClient - The GLContextReal holds the GPUTiming class and is the
//     factory that creates GPUTimingClient objects. If a user would like to
//     obtain various GPU times they would access CreateGPUTimingClient() from
//     their GLContext and use the returned object for their timing calls.
//     Each virtual context as well as any other classes which need GPU times
//     will hold one of these. When they want to time a set of GL commands they
//     will create GPUTimer objects.
//   GPUTimer - Once a user decides to time something, the user creates a new
//     GPUTimer object from a GPUTimingClient and issue Start() and Stop() calls
//     around various GL calls. Once IsAvailable() returns true, the GPU times
//     will be available through the various time stamp related functions.
//     The constructor and destructor of this object handles the actual
//     creation and deletion of the GL Queries within GL.

namespace gfx {

class GLContextReal;
class GPUTimingClient;
class GPUTimingImpl;
class QueryResult;

class GPUTiming {
public:
    enum TimerType {
        kTimerTypeInvalid = -1,

        kTimerTypeEXT, // EXT_timer_query
        kTimerTypeARB, // ARB_timer_query
        kTimerTypeDisjoint // EXT_disjoint_timer_query
    };

    virtual ~GPUTiming();

protected:
    friend std::default_delete<GPUTiming>;
    friend class GLContextReal;

    static GPUTiming* CreateGPUTiming(GLContextReal* context);

    GPUTiming();
    // virtual ~GPUTiming();

    virtual scoped_refptr<GPUTimingClient> CreateGPUTimingClient() = 0;

    DISALLOW_COPY_AND_ASSIGN(GPUTiming);
};

// Class to compute the amount of time it takes to fully
// complete a set of GL commands
class GL_EXPORT GPUTimer {
public:
    static void DisableTimestampQueries();

    ~GPUTimer();

    // Destroy the timer object. This must be explicitly called before destroying
    // this object.
    void Destroy(bool have_context);

    // Clears current queries.
    void Reset();

    // Start an instant timer, start and end will be equal.
    void QueryTimeStamp();

    // Start a timer range.
    void Start();
    void End();

    bool IsAvailable();

    void GetStartEndTimestamps(int64_t* start, int64_t* end);
    int64_t GetDeltaElapsed();

private:
    friend class GPUTimingClient;

    explicit GPUTimer(scoped_refptr<GPUTimingClient> gpu_timing_client,
        bool use_elapsed_timer);

    bool use_elapsed_timer_ = false;
    enum TimerState {
        kTimerState_Ready,
        kTimerState_WaitingForEnd,
        kTimerState_WaitingForResult,
        kTimerState_ResultAvailable
    } timer_state_
        = kTimerState_Ready;
    scoped_refptr<GPUTimingClient> gpu_timing_client_;
    scoped_refptr<QueryResult> time_stamp_result_;
    scoped_refptr<QueryResult> elapsed_timer_result_;

    DISALLOW_COPY_AND_ASSIGN(GPUTimer);
};

// GPUTimingClient contains all the gl timing logic that is not specific
// to a single GPUTimer.
class GL_EXPORT GPUTimingClient
    : public base::RefCounted<GPUTimingClient> {
public:
    explicit GPUTimingClient(GPUTimingImpl* gpu_timing = nullptr);

    scoped_ptr<GPUTimer> CreateGPUTimer(bool prefer_elapsed_time);
    bool IsAvailable();

    const char* GetTimerTypeName() const;

    // CheckAndResetTimerErrors has to be called before reading timestamps
    // from GPUTimers instances and after making sure all the timers
    // were available.
    // If the returned value is false, all the previous timers should be
    // discarded.
    bool CheckAndResetTimerErrors();

    int64_t GetCurrentCPUTime();
    void SetCpuTimeForTesting(const base::Callback<int64_t(void)>& cpu_time);

    bool IsForceTimeElapsedQuery();
    void ForceTimeElapsedQuery();

private:
    friend class base::RefCounted<GPUTimingClient>;
    friend class GPUTimer;

    virtual ~GPUTimingClient();

    GPUTimingImpl* gpu_timing_;
    GPUTiming::TimerType timer_type_ = GPUTiming::kTimerTypeInvalid;
    uint32_t disjoint_counter_ = 0;

    DISALLOW_COPY_AND_ASSIGN(GPUTimingClient);
};

} // namespace gfx

#endif // UI_GL_GPU_TIMING_H_
